home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / gnome-games-data / gnome_sudoku / sudoku_maker.py < prev    next >
Encoding:
Python Source  |  2009-04-14  |  23.4 KB  |  632 lines

  1. # -*- coding: utf-8 -*-
  2. import sys
  3. import os, shutil
  4. import os.path
  5. import sudoku
  6. import random
  7. import pickle
  8. import time
  9. import pausable
  10. import threading
  11. import saver
  12. from gettext import gettext as _
  13. from defaults import *
  14.  
  15. class SudokuGenerator:
  16.  
  17.     """A class to generate new Sudoku Puzzles."""
  18.  
  19.     def __init__ (self, start_grid=None, clues=2, group_size=9):
  20.         self.generated = []
  21.         self.clues = clues
  22.         self.all_coords = []
  23.         self.group_size = group_size
  24.         for x in range(self.group_size):
  25.             for y in range(self.group_size):
  26.                 self.all_coords.append((x,y))
  27.         if start_grid:
  28.             self.start_grid = sudoku.SudokuGrid(start_grid)
  29.         else:
  30.             try:
  31.                 self.start_grid = self.generate_grid()
  32.             except:
  33.                 self.start_grid = self.generate_grid()
  34.         self.puzzles = []
  35.         self.rated_puzzles = []
  36.  
  37.     def average_difficulty (self):
  38.         difficulties = [i[1].value for i in self.rated_puzzles]
  39.         if difficulties:
  40.             return sum(difficulties)/len(difficulties)
  41.  
  42.     def generate_grid (self):
  43.         self.start_grid = sudoku.SudokuSolver(verbose=False,group_size=self.group_size)
  44.         self.start_grid.solve()
  45.         return self.start_grid
  46.  
  47.     def reflect (self, x, y, axis=1):
  48.         #downward sloping
  49.         upper = self.group_size - 1
  50.         # reflect once...
  51.         x,y = upper - y,upper-x
  52.         # reflect twice...
  53.         return y, x
  54.             
  55.     def make_symmetric_puzzle (self):
  56.         nclues = self.clues/2
  57.         buckshot = set(random.sample(self.all_coords,nclues))
  58.         new_puzzle = sudoku.SudokuGrid(verbose=False,group_size=self.group_size)
  59.         reflections = set()
  60.         for x,y in buckshot:
  61.             reflection = self.reflect(x,y)
  62.             if reflection:
  63.                 nclues += 1
  64.                 reflections.add(reflection)
  65.         buckshot = buckshot | reflections # unite our sets
  66.         remaining_coords = set(self.all_coords) - set(buckshot)
  67.         while len(buckshot) < self.clues:
  68.             coord = random.sample(remaining_coords,1)[0]
  69.             buckshot.add(coord)
  70.             reflection = self.reflect(*coord)
  71.             if reflection:
  72.                 buckshot.add(reflection)
  73.             remaining_coords = remaining_coords - buckshot
  74.         return self.make_puzzle_from_coords(buckshot)
  75.  
  76.     def make_puzzle (self):
  77.         buckshot = random.sample(self.all_coords,self.clues)
  78.         while buckshot in self.generated:
  79.             buckshot = random.sample(self.all_coords,self.clues)
  80.         return self.make_puzzle_from_coords(buckshot)
  81.  
  82.     def make_puzzle_from_coords (self, buckshot):
  83.         new_puzzle = sudoku.SudokuGrid(verbose=False,group_size=self.group_size)
  84.         self.generated.append(set(buckshot))
  85.         for x,y in buckshot:
  86.             new_puzzle.add(x,y,self.start_grid._get_(x,y))
  87.         self.puzzles.append(new_puzzle)
  88.         return new_puzzle
  89.     
  90.     def make_puzzle_by_boxes (self,
  91.                               skew_by=0.0,
  92.                               max_squares=None,):
  93.         """Make a puzzle paying attention to evenness of clue
  94.         distribution.
  95.  
  96.         If skew_by is 0, we distribute our clues as evenly as possible
  97.         across boxes.  If skew by is 1.0, we make the distribution of
  98.         clues as uneven as possible. In other words, if we had 27
  99.         boxes for a 9x9 grid, a skew_by of 0 would put exactly 3 clues
  100.         in each 3x3 grid whereas a skew_by of 1.0 would completely
  101.         fill 3 3x3 grids with clues.
  102.  
  103.         We believe this skewing may have something to do with how
  104.         difficult a puzzle is to solve. By toying with the ratios,
  105.         this method may make it considerably easier to generate
  106.         difficult or easy puzzles.
  107.         """
  108.         # Number of total boxes
  109.         nboxes = len(self.start_grid.boxes)
  110.         # If no max is given, we calculate one based on our skew_by --
  111.         # a skew_by of 1 will always produce full squares, 0 will
  112.         # produce the minimum fullness, and between between in
  113.         # proportion to its betweenness.
  114.         if not max_squares:
  115.             max_squares = self.clues / nboxes
  116.             max_squares += int((nboxes-max_squares)*skew_by)
  117.         clued = 0
  118.         # nclues will be a list of the number of clues we want per
  119.         # box.
  120.         nclues = []
  121.         for n in range(nboxes):
  122.             # Make sure we'll have enough clues to fill our target
  123.             # number, regardless of our calculation of the current max
  124.             minimum = (self.clues-clued)/(nboxes-n)
  125.             if max_squares < minimum:
  126.                 cls = minimum
  127.             else:
  128.                 cls = int(max_squares)
  129.             clues = max_squares
  130.             if clues > (self.clues - clued):
  131.                 clues = self.clues - clued
  132.             nclues.append(int(clues))
  133.             clued += clues
  134.             if skew_by:
  135.                 # Reduce our number of squares proportionally to
  136.                 # skewiness. 
  137.                 max_squares = round(max_squares * skew_by)
  138.         # shuffle ourselves...
  139.         random.shuffle(nclues)
  140.         buckshot = []
  141.         for i in range(nboxes):
  142.             if nclues[i]:
  143.                 buckshot.extend(
  144.                     random.sample(self.start_grid.box_coords[i],
  145.                                   nclues[i])
  146.                     )
  147.         return self.make_puzzle_from_coords(buckshot)
  148.  
  149.     def assess_difficulty (self, sudoku_grid):
  150.         try:
  151.             solver = sudoku.SudokuRater(sudoku_grid,verbose=False,group_size=self.group_size)
  152.             d = solver.difficulty()
  153.             self.rated_puzzles.append((sudoku_grid,d))
  154.             return d
  155.         except:
  156.             print 'Impossible!'
  157.             print 'Puzzle was:'
  158.             print solver.virgin
  159.             print 'Solution: ',
  160.             print self.start_grid
  161.             print 'Puzzle foobared in following state:',
  162.             print solver
  163.             raise
  164.  
  165.     def is_unique (self, sudoku_grid):
  166.         """If puzzle is unique, return its difficulty.
  167.  
  168.         Otherwise, return None."""
  169.         solver = sudoku.SudokuRater(sudoku_grid,verbose=False,group_size=self.group_size)
  170.         if solver.has_unique_solution():
  171.             return solver.difficulty()
  172.         else:
  173.             return None
  174.         
  175.     def generate_puzzle_for_difficulty (self,
  176.                                         lower_target=0.3,
  177.                                         upper_target=0.5,
  178.                                         max_tries = 100,
  179.                                         by_box=False,
  180.                                         by_box_kwargs={}):
  181.         for i in range(max_tries):
  182.             if by_box:
  183.                 puz = self.make_puzzle_by_boxes(**by_box_kwargs)
  184.             else:
  185.                 puz = self.make_puzzle()
  186.             d = self.assess_difficulty(puz.grid)
  187.             if (d and (not lower_target or d.value > lower_target) and\
  188.                (not upper_target or
  189.                 d.value < upper_target)):
  190.                 return puz,d
  191.         else: return None,None
  192.  
  193.     def make_unique_puzzle (self, symmetrical=True, strict_number_of_clues=False):
  194.         if symmetrical:
  195.             puz = self.make_symmetric_puzzle()
  196.         else:
  197.             puz = make_puzzle()
  198.         diff = False
  199.         if self.clues > 10:
  200.             clues = self.clues - 8
  201.         else:
  202.             clues = 2
  203.         while 1:
  204.             solver = sudoku.SudokuRater(puz.grid,verbose=False,group_size=self.group_size)
  205.             if solver.has_unique_solution():
  206.                 diff = solver.difficulty()
  207.                 #raw_input('Unique puzzle!')
  208.                 break
  209.             # Otherwise...
  210.             crumb = solver.breadcrumbs[-1]
  211.             fill_in = [(crumb.x,crumb.y)]
  212.             reflection = self.reflect(crumb.x,crumb.y)
  213.             if reflection: fill_in.append(reflection)
  214.             for x,y in fill_in:
  215.                 solver.virgin._set_(x,y,self.start_grid._get_(x,y))
  216.             puz = sudoku.SudokuGrid(solver.virgin.grid, verbose=False)
  217.             #print 'Not unique, adding ',fill_in
  218.             clues += len(fill_in)
  219.             if strict_number_of_clues==True and clues > self.clues: return None
  220.             #print clues, "clues..."
  221.             #raw_input('Continue: ')
  222.         # make sure we have the proper number of clues
  223.         if strict_number_of_clues:
  224.             changed=False
  225.             while clues < self.clues:
  226.                 x,y=random.randint(0,8),random.randint(0,8)
  227.                 while puz._get_(x,y): x,y=random.randint(0,8),random.randint(0,8)
  228.                 puz._set_(x,y,self.start_grid._get_(x,y))
  229.                 clues += 1
  230.                 reflection = self.reflect(x,y)
  231.                 if reflection:
  232.                     puz._set_(x,y,self.start_grid._get_(x,y))
  233.                     clues += 1
  234.                 changed=True
  235.             if changed: diff = sudoku.SudokuRater(puz.grid,
  236.                                                   verbose=False,
  237.                                                   group_size=self.group_size).difficulty()
  238.         return puz,diff
  239.  
  240.     def make_unique_puzzles (self, n=10, ugargs={}):
  241.         ug = self.unique_generator(**ugargs)
  242.         ret = []
  243.         for i in range(n):
  244.             #print 'Working on puzzle ',i
  245.             ret.append(ug.next())
  246.             #print 'Got one!'
  247.         return ret
  248.  
  249.     def unique_generator (self,
  250.                           symmetrical=True,
  251.                           strict_number_of_clues=False,
  252.                           by_box=False,
  253.                           by_box_kwargs={}):
  254.         while 1:
  255.             result = self.make_unique_puzzle(
  256.                 symmetrical=symmetrical,
  257.                 strict_number_of_clues=strict_number_of_clues)
  258.             if result: yield result
  259.  
  260.     def generate_puzzles (self, n=10,
  261.                           symmetrical=True,
  262.                           by_box=False,
  263.                           by_box_kwargs={}):
  264.         ret = []
  265.         for i in range(n):
  266.             #print 'Generating puzzle ',i
  267.             if symmetrical:
  268.                 puz = self.make_symmetric_puzzle()
  269.             elif by_box:
  270.                 puz = self.make_puzzle_by_boxes(**by_box_kwargs)
  271.             else:
  272.                 puz = self.make_puzzle()
  273.             #print 'Assessing puzzle ',puz
  274.             try:
  275.                 d=self.assess_difficulty(puz.grid)
  276.             except:
  277.                 raise
  278.             if d:
  279.                 ret.append((puz,d))
  280.         ret.sort(lambda a,b: a[1].value>b[1].value and 1 or a[1].value<b[1].value and -1 or 0)
  281.         return ret
  282.  
  283.  
  284. class InterruptibleSudokuGenerator (SudokuGenerator):
  285.     def __init__ (self,*args,**kwargs):
  286.         self.paused = False
  287.         self.terminated = False
  288.         SudokuGenerator.__init__(self,*args,**kwargs)
  289.     def work (self,*args,**kwargs):
  290.         self.unterminate()
  291.         SudokuGenerator(self,*args,**kwargs)
  292.  
  293. pausable.make_pausable(InterruptibleSudokuGenerator)
  294.  
  295.  
  296. class SudokuMaker:
  297.  
  298.     """A class to create unique, symmetrical sudoku puzzles."""
  299.  
  300.     def __init__ (self,
  301.                   generator_args={'clues':27,
  302.                                   'group_size':9},
  303.                   puzzle_maker_args={'symmetrical':True},
  304.                   batch_size = 5,
  305.                   pickle_to = os.path.join(DATA_DIR,'puzzles')):
  306.         self.pickle_to = pickle_to
  307.         self.paused = False
  308.         self.terminated = False
  309.         self.generator_args = generator_args
  310.         self.puzzle_maker_args = puzzle_maker_args
  311.         self.batch_size = batch_size
  312.         self.load()
  313.         self.all_puzzles = {}
  314.         self.played = self.get_pregenerated('finished')
  315.         self.n_available_sudokus = {'easy':None,'medium':None,'hard':None,'very hard':None}
  316.  
  317.     def load (self):
  318.         try:
  319.             os.makedirs(self.pickle_to)
  320.         except os.error, e:
  321.             if e.errno != errno.EEXIST:
  322.                 return
  323.         for cat in sudoku.DifficultyRating.categories:
  324.             source = os.path.join(os.path.join(PUZZLE_DIR),cat.replace(' ','_'))
  325.             target = os.path.join(self.pickle_to, cat.replace(' ','_'))
  326.             if not os.path.exists(target):
  327.                 try:
  328.                     shutil.copy(source, target)
  329.                 except:
  330.                     print 'Problem copying base puzzles'
  331.                     print 'Attempted to copy from %s to %s' % (source, target)
  332.                 
  333.     def get_pregenerated (self, difficulty):
  334.         fname = os.path.join(self.pickle_to, difficulty.replace(' ','_'))
  335.         try:
  336.             lines = file(fname).readlines()
  337.         except IOError, e:
  338.             if e.errno != errno.ENOENT:
  339.                 print 'Error reading pregenerated puzzles for difficulty \'%s\': %s' % (difficulty, e.strerror)
  340.             return []
  341.         else:
  342.             return [line.strip() for line in lines]
  343.         
  344.     def get_new_puzzle (self, difficulty, new=True):
  345.         """Return puzzle with difficulty near difficulty.
  346.  
  347.         If new is True, we return only unplayed puzzles.
  348.         Return a tuple containing a new puzzle and difficulty object.
  349.         """
  350.         val_cat = sudoku.get_difficulty_category(difficulty)
  351.         if not val_cat:
  352.             print 'WARNING, no val cat for difficulty:',difficulty
  353.             if val_cat > 1: val_cat = 'very hard'
  354.             else: val_cat = 'easy'
  355.         puzzles = []
  356.  
  357.         lines = self.get_pregenerated(val_cat)
  358.         closest = 10000000000000,None
  359.         for l in lines:
  360.             if len(l) == 0:
  361.                 print 'Warning: file %s contains an empty line'%fname
  362.                 continue
  363.             if not l.find('\t')>=0:
  364.                 print 'Warning: line "%s" of file %s has no tab character.'%(l,fname)
  365.                 continue
  366.             puzzle,diff = l.split('\t')
  367.             if new and (puzzle in self.played): continue
  368.             if not sudoku.is_valid_puzzle(puzzle):
  369.                 print 'WARNING: invalid puzzle %s in file %s'%(puzzle,fname)
  370.                 continue
  371.             diff = float(diff)
  372.             closeness_to_target = abs(diff - difficulty)
  373.             if closest[0] > closeness_to_target:
  374.                 closest = diff,puzzle
  375.         return closest[1],sudoku.SudokuRater(
  376.             sudoku.sudoku_grid_from_string(closest[1]).grid
  377.             ).difficulty()
  378.  
  379.     def n_puzzles (self, difficulty_category=None, new=True):
  380.         if not difficulty_category:
  381.             return sum([self.n_puzzles(c,new=new) for c in sudoku.DifficultyRating.categories])
  382.         else:
  383.             if self.n_available_sudokus[difficulty_category]:
  384.                 return self.n_available_sudokus[difficulty_category]
  385.             lines = self.get_pregenerated(difficulty_category)
  386.             count = 0
  387.             for line in lines:
  388.                 if (not new) or line.split('\t')[0] not in self.played:
  389.                     count+=1
  390.             self.n_available_sudokus[difficulty_category] = count
  391.             return self.n_available_sudokus[difficulty_category]
  392.  
  393.     def list_puzzles (self, difficulty_category=None, new=True):
  394.         """Return a list of all puzzles we have generated.
  395.         """
  396.         puzzle_list = []
  397.         if not difficulty_category:
  398.             for c in sudoku.DifficultyRating.categories:
  399.                 puzzle_list.extend(self.list_puzzles(c,new=new))
  400.         else:
  401.             lines = self.get_pregenerated(difficulty_category)
  402.             for l in lines:
  403.                 puzzle = l.split('\t')[0]
  404.                 if (not new) or puzzle not in self.played:
  405.                     puzzle_list.append(puzzle)
  406.         return puzzle_list
  407.  
  408.     def get_puzzles_random (self, n, levels, new=True, exclude=[]):
  409.         """Return a list of n puzzles and difficulty values (as floats).
  410.  
  411.         The puzzles will correspond as closely as possible to levels.
  412.         If new, we only return puzzles not yet played.
  413.         """
  414.         if not n: return []
  415.         assert(levels)
  416.         puzzles = []
  417.         # Open files to read puzzles...
  418.         puzzles_by_level = {}; files = {}
  419.         for l in levels:
  420.             puzzles_by_level[l] = self.get_pregenerated(l)
  421.             random.shuffle(puzzles_by_level[l])
  422.         i = 0; il = 0
  423.         n_per_level = {}
  424.         finished = []
  425.         while i < n and len(finished) < len(levels):
  426.             if il >= len(levels): il = 0
  427.             lev = levels[il]
  428.             # skip any levels that we've exhausted
  429.             if lev in finished:
  430.                 il += 1
  431.                 continue
  432.             try:
  433.                 line = puzzles_by_level[lev].pop()
  434.             except IndexError:
  435.                 finished.append(lev)
  436.             else:
  437.                 try:
  438.                     p,d = line.split('\t')
  439.                 except ValueError:
  440.                     print 'WARNING: invalid line %s in file %s'%(line,files[lev])
  441.                     continue
  442.                 if sudoku.is_valid_puzzle(p):
  443.                     if (p not in exclude) and (not new or p not in self.played):
  444.                         puzzles.append((p,float(d)))
  445.                         i += 1
  446.                 else:
  447.                     print 'WARNING: invalid puzzle %s in file %s'%(p,files[lev])
  448.             il += 1
  449.         if i < n:
  450.             print 'WARNING: Not able to provide %s puzzles in levels %s'%(n,levels)
  451.             print 'WARNING: Generate more puzzles if you really need this many puzzles!'
  452.         return puzzles    
  453.  
  454.     def get_puzzles (self, n, levels, new=True, randomize=True,
  455.                      exclude=[]):
  456.         """Return a list of n puzzles and difficulty values (as floats).
  457.  
  458.         The puzzles will correspond as closely as possible to levels.
  459.         If new, we only return puzzles not yet played.
  460.         """
  461.         if randomize: return self.get_puzzles_random(n,levels,new=new,exclude=exclude)
  462.         if not n: return []
  463.         assert(levels)
  464.         puzzles = []
  465.         
  466.         # Open files to read puzzles...
  467.         files = {}
  468.         for l in levels:
  469.             files[l] = self.get_pregenerated(l)
  470.  
  471.         i = 0; il = 0
  472.         n_per_level = {}
  473.         finished = []
  474.         while i < n and len(finished) < len(levels):
  475.             if il >= len(levels): il = 0
  476.             lev = levels[il]
  477.             # skip any levels that we've exhausted
  478.             if lev in finished:
  479.                 il += 1
  480.                 continue
  481.             
  482.             if len(files[lev]) == 0:
  483.                 finished.append(lev)
  484.             else:
  485.                 line = files[lev][0]
  486.                 files[lev] = files[lev][1:]
  487.                 try:
  488.                     p,d = line.split('\t')
  489.                 except ValueError:
  490.                     print 'WARNING: invalid line %s in file %s'%(line,files[lev])
  491.                     continue
  492.                 if sudoku.is_valid_puzzle(p):
  493.                     if (p not in exclude) and (not new or p not in self.played):
  494.                         puzzles.append((p,float(d)))
  495.                         i += 1
  496.                 else:
  497.                     print 'WARNING: invalid puzzle %s in file %s'%(p,files[lev])
  498.             il += 1
  499.         if i < n:
  500.             print 'WARNING: Not able to provide %s puzzles in levels %s'%(n,levels)
  501.             print 'WARNING: Generate more puzzles if you really need this many puzzles!'
  502.  
  503.         return puzzles    
  504.  
  505.     # End convenience methods for accessing puzzles we've created
  506.  
  507.     # Methods for creating new puzzles
  508.  
  509.     def make_batch (self, diff_min=None, diff_max=None):
  510.         self.new_generator = InterruptibleSudokuGenerator(**self.generator_args)
  511.         key = self.new_generator.start_grid.to_string()        
  512.         #while 
  513.         #    self.new_generator = InterruptibleSudokuGenerator(**self.generator_args)
  514.         #    key = self.new_generator.start_grid.to_string()
  515.         #print 'We have our solution grid'
  516.         #self.puzzles_by_solution[key]=[]
  517.         ug = self.new_generator.unique_generator(**self.puzzle_maker_args)
  518.         open_files = {}
  519.         for n in range(self.batch_size):
  520.             #print 'start next item...',n
  521.             puz,diff = ug.next()
  522.             #print "GENERATED ",puz,diff
  523.             if ((not diff_min or diff.value >= diff_min)
  524.                 and
  525.                 (not diff_max or diff.value <= diff_max)):
  526.                 puzstring = puz.to_string()
  527.                 # self.puzzles_by_solution[key].append((puzstring,diff))
  528.                 # self.solutions_by_puzzle[puzstring]=key
  529.                 # self.all_puzzles[puzstring] = diff
  530.                 # self.names[puzstring] = self.get_puzzle_name(_('Puzzle'))
  531.                 outpath = os.path.join(self.pickle_to,
  532.                                        diff.value_category().replace(' ','_'))
  533.                 # Read through the existing file and make sure we're
  534.                 # not a duplicate puzzle
  535.                 existing = self.get_pregenerated(diff.value_category())
  536.                 if not puzstring in existing:
  537.                     try:
  538.                         outfi = file(outpath,'a')
  539.                         outfi.write(puzstring+'\t'+str(diff.value)+'\n')
  540.                         outfi.close()
  541.                         self.n_available_sudokus[diff.value_category()]+=1
  542.                     except IOError, e:
  543.                         print 'Error appending pregenerated puzzle: %s' % e.strerror
  544.  
  545.     def pause (self, *args):
  546.         if hasattr(self,'new_generator'): self.new_generator.pause()
  547.         self.paused = True
  548.  
  549.     def resume (self, *args):
  550.         if hasattr(self,'new_generator'): self.new_generator.resume()
  551.         self.paused = False
  552.  
  553.     def stop (self, *args):
  554.         if hasattr(self,'new_generator'): self.new_generator.terminate()
  555.         self.terminated = True
  556.  
  557.     def hesitate (self):
  558.         while self.paused:
  559.             if self.terminated: break
  560.             time.sleep(1)
  561.  
  562.     def work (self, limit = None, diff_min=None, diff_max=None):
  563.         """Intended to be called as a worker thread, make puzzles!"""
  564.         self.terminated = False
  565.         if hasattr(self,'new_generator'): self.new_generator.termintaed = False
  566.         self.paused = False
  567.         generated = 0
  568.         while not limit or generated < limit:
  569.             if self.terminated:
  570.                 break
  571.             if self.paused:
  572.                 self.hesitate()
  573.             try:
  574.                 self.make_batch(diff_min=diff_min,
  575.                                 diff_max=diff_max)
  576.             except:
  577.                 raise
  578.             else:
  579.                 generated += 1
  580.  
  581.     def get_difficulty (self, puz):
  582.         return sudoku.SudokuRater(puz).difficulty()
  583.  
  584.         
  585. if __name__ == '__main__':
  586.     import time
  587.     #puzzles=sg.generate_puzzles(30)
  588.     #
  589.     #print sg.make_symmetric_puzzle()
  590.     #unique_maker = sg.unique_generator()
  591.     #unique = []
  592.     #for n in range(10):
  593.     #    unique.append(unique_maker.next())
  594.     #    print 'Generated Unique...'
  595.     #unique_puzzles=filter(lambda x: sudoku.SudokuSolver(x[0].grid,verbose=False).has_unique_solution(),puzzles)
  596.     sm = SudokuMaker()
  597.     #st = SudokuTracker(sm)
  598. elif False:    
  599.     usage="""Commands are:
  600.     run: Run sudoku-maker
  601.     len: \# of puzzles generated
  602.     pause: pause
  603.     resume: resume
  604.     terminate: kill thread
  605.     quit: to quit
  606.     """
  607.     print usage
  608.     while 1:
  609.         inp = raw_input('choose:')
  610.         if inp=='run':
  611.             t=threading.Thread(target=sm.make_batch)
  612.             t.start()
  613.         elif inp=='show':
  614.             print sm.puzzles
  615.         elif inp=='len':
  616.             print len(sm.puzzles)
  617.         elif inp=='pause':
  618.             sm.new_generator.pause()
  619.         elif inp=='resume':
  620.             sm.new_generator.resume()
  621.         elif inp=='terminate':
  622.             sm.new_generator.terminate()
  623.         elif inp=='quit':
  624.             sm.new_generator.terminate()
  625.             break
  626.         else:
  627.             try:
  628.                 getattr(sm,inp)()
  629.             except:
  630.                 print usage
  631.         
  632.